package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"path"
	"strings"

	"gitee.com/hlwqds/go-project/tag-service/global"
	"gitee.com/hlwqds/go-project/tag-service/middleware"
	"gitee.com/hlwqds/go-project/tag-service/pkg/swagger"
	"gitee.com/hlwqds/go-project/tag-service/pkg/tracer"
	pb "gitee.com/hlwqds/go-project/tag-service/proto"

	"gitee.com/hlwqds/go-project/tag-service/server"
	"gitee.com/hlwqds/go-project/tag-service/server/cmd"
	assetfs "github.com/elazarl/go-bindata-assetfs"
	ggm "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/reflection"
	"google.golang.org/grpc/status"
)

/*
	serve at diff port

func RunGRPCServer(port string) error {
	s := grpc.NewServer()
	pb.RegisterTagServiceServer(s, NewTagServer())
	reflection.Register(s)
	lis, err := net.Listen("tcp", ":"+port)
	if err != nil {
		return err
	}
	return s.Serve(lis)

}

func RunHTTPServer(port string) error {
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/ping", func(rw http.ResponseWriter, r *http.Request) {
		_, _ = rw.Write([]byte("pong"))
	})
	return http.ListenAndServe(":"+port, serveMux)
}

func main() {
	cmd.Execute()
	errCh := make(chan error)
	go func() {
		err := RunGRPCServer(cmd.GRPCPort)
		if err != nil {
			errCh <- err
		}
	}()
	go func() {
		err := RunHTTPServer(cmd.HTTPPort)
		if err != nil {
			errCh <- err
		}
	}()
	select {
	case err := <-errCh:
		log.Fatalf("Run Server err: %v", err)
	}
}

*/

/*
	gRPC和http使用同一个端口，但是业务仍需要实现两次
	func RunTcpServer(port string) (net.Listener, error) {
	return net.Listen("tcp", ":"+port)
}

func RunGRPCServer() *grpc.Server {
	s := grpc.NewServer()
	pb.RegisterTagServiceServer(s, NewTagServer())
	reflection.Register(s)
	return s
}

func RunHTTPServer(port string) *http.Server {
	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/ping", func(rw http.ResponseWriter, r *http.Request) {
		_, _ = rw.Write([]byte("pong"))
	})
	return &http.Server{
		Addr:    ":" + port,
		Handler: serveMux,
	}
}

func main() {
	l, err := RunTcpServer(cmd.SamePort)
	if err != nil {
		log.Fatalf("RunTcpServer err: %v", err)
	}
	m := cmux.New(l)
	//grpc处理content-type为grpc的请求
	grpcL := m.MatchWithWriters(
		cmux.HTTP2MatchHeaderFieldPrefixSendSettings(
			"content-type",
			"application/grpc",
		),
	)
	httpL := m.Match(cmux.HTTP1Fast())

	grpcS := RunGRPCServer()
	httpS := RunHTTPServer(cmd.SamePort)
	go grpcS.Serve(grpcL)
	go httpS.Serve(httpL)
	err = m.Serve()
	if err != nil {
		log.Fatalf("Run Serve err: %v", err)
	}
}

*/
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
	//拦截流浪并且根据不同的类型重定向到对应的handler
	return h2c.NewHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		//http2协议，并且使grpc
		if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-type"), "application/grpc") {
			grpcServer.ServeHTTP(rw, r)
		} else {
			otherHandler.ServeHTTP(rw, r)
		}
	}), &http2.Server{})
}

func RunServer(port string) error {
	httpMux := RunHTTPServer()
	grpcS := runGRPCServer()
	gatewayMux := runGRPCGatewayServer(port)
	httpMux.Handle("/", gatewayMux)
	return http.ListenAndServe(":"+port, grpcHandlerFunc(grpcS, httpMux))
}

func RunHTTPServer() *http.ServeMux {
	serverMux := http.NewServeMux()
	serverMux.HandleFunc("/ping", func(rw http.ResponseWriter, r *http.Request) {
		_, _ = rw.Write([]byte("pong"))
	})

	prefix := "/swagger-ui/"
	fileServer := http.FileServer(&assetfs.AssetFS{
		Asset:    swagger.Asset,
		AssetDir: swagger.AssetDir,
		Prefix:   "third_party/swagger-ui",
	})
	serverMux.HandleFunc("/swagger/", func(rw http.ResponseWriter, r *http.Request) {
		if !strings.HasSuffix(r.URL.Path, "swagger.json") {
			http.NotFound(rw, r)
			return
		}
		p := strings.TrimPrefix(r.URL.Path, "/swagger/")
		//重定位到proto目录
		p = path.Join("proto", p)
		http.ServeFile(rw, r, p)
	})
	serverMux.Handle(prefix, http.StripPrefix(prefix, fileServer))
	return serverMux
}

func runGRPCServer() *grpc.Server {
	opts := []grpc.ServerOption{
		grpc.UnaryInterceptor(ggm.ChainUnaryServer(
			middleware.AccessLog,
			middleware.ErrorLog,
			middleware.Recovery,
			middleware.ServerTracing,
		)),
	}
	s := grpc.NewServer(opts...)
	pb.RegisterTagServiceServer(s, &server.TagServer{})
	reflection.Register(s)
	return s
}

func runGRPCGatewayServer(port string) *runtime.ServeMux {
	endpoint := "0.0.0.0:" + port
	runtime.HTTPError = grpcGatewayError
	gwmux := runtime.NewServeMux()
	dopts := []grpc.DialOption{grpc.WithInsecure()}
	_ = pb.RegisterTagServiceHandlerFromEndpoint(context.Background(), gwmux, endpoint, dopts)
	return gwmux
}

type httpError struct {
	Code    int32  `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func grpcGatewayError(
	ctx context.Context,
	_ *runtime.ServeMux,
	marshaler runtime.Marshaler,
	w http.ResponseWriter,
	_ *http.Request,
	err error) {
	s, ok := status.FromError(err)
	if !ok {
		s = status.New(codes.Unknown, err.Error())
	}
	httpError := httpError{Code: int32(s.Code()), Message: s.Message()}
	details := s.Details()
	for _, detail := range details {
		if v, ok := detail.(*pb.Error); ok {
			httpError.Code = v.Code
			httpError.Message = v.Message
		}
	}
	resp, _ := json.Marshal(httpError)
	w.Header().Set("Content-type", marshaler.ContentType())
	w.WriteHeader(runtime.HTTPStatusFromCode(s.Code()))
	_, _ = w.Write(resp)

}

func main() {
	cmd.Execute()

	//创建后返回go: []string("programming")
	metadata.New(map[string]string{"go": "programming"})
	//创建后返回go: []string("programming", "test")
	//tour: []string("book")
	metadata.Pairs(
		"go", "programming",
		"tour", "book",
		"go", "test",
	)
	ctx := context.Background()
	md := metadata.Pairs(
		"go", "programming",
		"tour", "book",
		"go", "test",
	)

	newCtx1 := metadata.NewOutgoingContext(ctx, md)
	newCtx1 = metadata.AppendToOutgoingContext(newCtx1, "append", "test")
	newCtx2 := metadata.NewIncomingContext(ctx, md)
	md1, ok := metadata.FromIncomingContext(newCtx2)
	if ok {
		fmt.Println(md1)
	}
	md2, ok := metadata.FromOutgoingContext(newCtx1)
	if ok {
		fmt.Println(md2)

	}

	RunServer(cmd.SamePort)
}
func setTracer() error {
	jaegerTracer, _, err := tracer.NewJaegerTracer(
		"tag-service",
		"119.45.147.122:6831",
	)
	if err != nil {
		return nil
	}
	global.Tracer = jaegerTracer
	return nil
}

func init() {
	setTracer()
}
